/*
 * Core.cpp
 *
 *  Created on: 2011/06/04
 *      Author: naoki
 */

#include "StdAfx.h"
#include "NTEngine/NTEngine.h"
#include "NTEngine/Core/Core.h"

#include "Base/src/android_app.h"

#include <jni.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <sys/resource.h>
#include <poll.h>
#include <pthread.h>
#include <sched.h>
#include <time.h>
#include <string>
#include <android/configuration.h>
#include <android/looper.h>
#include <android/native_activity.h>
#include <android/sensor.h>
#include <android/log.h>

#ifndef NDEBUG
#ifdef LOGI
#undef LOGI
#endif
#define LOGI(...) ((void)__android_log_print(ANDROID_LOG_INFO, "Android", __VA_ARGS__))
#else
#define LOGI(...)
#endif

namespace NTEngine {
using namespace std;

namespace { // 無名

// 時間取得用関数。高解像度タイマーを使用。
#define CLOCK_TIMEGET( timespec_inst ) clock_gettime( CLOCK_MONOTONIC_HR, &timespec_inst )

// メインループ用
void android_main( android_app* );

static void free_saved_state( android_app* app ) {
    pthread_mutex_lock( &app->mutex );
    if ( app->savedState != NULL ) {
    	unsigned char* p = static_cast< unsigned char* >( app->savedState );
        SAFE_DELETE_ARRAY( p );
        app->savedState = NULL;
        app->savedStateSize = 0;
    }
    pthread_mutex_unlock( &app->mutex );
}

int8_t android_app_read_cmd( android_app* app ) {
    int8_t cmd;
    if ( read( app->msgread, &cmd, sizeof( cmd ) ) == sizeof( cmd ) ) {
        switch ( cmd ) {
            case APP_CMD_SAVE_STATE:
                free_saved_state( app );
                break;
        }
        return cmd;
    } else {
        LOGI( "No data on command pipe!" );
    }
    return -1;
}

static void print_cur_config( android_app* app ) {
    char lang[2], country[2];
    AConfiguration_getLanguage( app->config, lang );
    AConfiguration_getCountry( app->config, country );

    LOGI( "Config: mcc=%d mnc=%d lang=%c%c cnt=%c%c orien=%d touch=%d dens=%d "
            "keys=%d nav=%d keysHid=%d navHid=%d sdk=%d size=%d long=%d "
            "modetype=%d modenight=%d",
            AConfiguration_getMcc( app->config ),
            AConfiguration_getMnc( app->config ),
            lang[0], lang[1], country[0], country[1],
            AConfiguration_getOrientation( app->config ),
            AConfiguration_getTouchscreen( app->config),
            AConfiguration_getDensity( app->config ),
            AConfiguration_getKeyboard( app->config ),
            AConfiguration_getNavigation( app->config ),
            AConfiguration_getKeysHidden( app->config ),
            AConfiguration_getNavHidden( app->config ),
            AConfiguration_getSdkVersion( app->config ),
            AConfiguration_getScreenSize( app->config ),
            AConfiguration_getScreenLong( app->config ),
            AConfiguration_getUiModeType( app->config ),
            AConfiguration_getUiModeNight( app->config ) );
}

void android_app_pre_exec_cmd( android_app* app, int8_t cmd ) {
    switch ( cmd ) {
        case APP_CMD_INPUT_CHANGED:
            LOGI( "APP_CMD_INPUT_CHANGED\n" );
            pthread_mutex_lock( &app->mutex );
            if ( app->inputQueue != NULL ) {
                AInputQueue_detachLooper( app->inputQueue );
            }
            app->inputQueue = app->pendingInputQueue;
            if ( app->inputQueue != NULL ) {
                LOGI( "Attaching input queue to looper" );
                AInputQueue_attachLooper( app->inputQueue,
                        app->looper, LOOPER_ID_INPUT, NULL,
                        &app->inputPollSource );
            }
            pthread_cond_broadcast( &app->cond );
            pthread_mutex_unlock( &app->mutex );
            break;

        case APP_CMD_INIT_WINDOW:
            LOGI( "APP_CMD_INIT_WINDOW\n" );
            pthread_mutex_lock( &app->mutex );
            app->window = app->pendingWindow;
            pthread_cond_broadcast( &app->cond );
            pthread_mutex_unlock( &app->mutex );
            break;

        case APP_CMD_TERM_WINDOW:
            LOGI( "APP_CMD_TERM_WINDOW\n" );
            pthread_cond_broadcast( &app->cond );
            break;

        case APP_CMD_RESUME:
        case APP_CMD_START:
        case APP_CMD_PAUSE:
        case APP_CMD_STOP:
        case APP_CMD_WINDOW_REDRAW_NEEDED: // add
            LOGI( "activityState=%d\n", cmd );
            pthread_mutex_lock( &app->mutex );
            app->activityState = cmd;
            pthread_cond_broadcast( &app->cond );
            pthread_mutex_unlock( &app->mutex );
            break;

        case APP_CMD_CONFIG_CHANGED:
            LOGI( "APP_CMD_CONFIG_CHANGED\n" );
            AConfiguration_fromAssetManager( app->config,
                    app->activity->assetManager );
            print_cur_config( app );
            break;

        case APP_CMD_DESTROY:
            LOGI( "APP_CMD_DESTROY\n" );
            app->destroyRequested = 1;
            break;
    }
}

void android_app_post_exec_cmd(struct android_app* android_app, int8_t cmd) {
    switch (cmd) {
        case APP_CMD_TERM_WINDOW:
            LOGI("APP_CMD_TERM_WINDOW\n");
            pthread_mutex_lock(&android_app->mutex);
            android_app->window = NULL;
            pthread_cond_broadcast(&android_app->cond);
            pthread_mutex_unlock(&android_app->mutex);
            break;

        case APP_CMD_SAVE_STATE:
            LOGI("APP_CMD_SAVE_STATE\n");
            pthread_mutex_lock(&android_app->mutex);
            android_app->stateSaved = 1;
            pthread_cond_broadcast(&android_app->cond);
            pthread_mutex_unlock(&android_app->mutex);
            break;

        case APP_CMD_RESUME:
            free_saved_state(android_app);
            break;
    }
}

static void android_app_destroy( android_app* app ) {
    LOGI( "android_app_destroy!" );
    free_saved_state( app );
    pthread_mutex_lock( &app->mutex );
    if ( app->inputQueue != NULL ) {
        AInputQueue_detachLooper( app->inputQueue );
    }
    AConfiguration_delete( app->config );
    app->destroyed = 1;
    pthread_cond_broadcast( &app->cond );
    pthread_mutex_unlock( &app->mutex );
    // Can't touch android_app object after this.
}

static void process_input( android_app* app, struct android_poll_source* source ) {
    AInputEvent* event = NULL;
    if ( AInputQueue_getEvent( app->inputQueue, &event ) >= 0 ) {
        if ( AInputQueue_preDispatchEvent( app->inputQueue, event ) ) {
            return;
        }
        int32_t handled = 0;
        if ( app->onInputEvent != NULL ) {
        	handled = app->onInputEvent( app, event );
        }
        AInputQueue_finishEvent( app->inputQueue, event, handled );
    } else {
        LOGI( "Failure reading next input event: %s\n", strerror( errno ) );
    }
}

static void process_cmd( android_app* app, struct android_poll_source* source ) {
    int8_t cmd = android_app_read_cmd( app );
    android_app_pre_exec_cmd( app, cmd );
    if ( app->onAppCmd != NULL ) {
    	app->onAppCmd( app, cmd );
    }
    android_app_post_exec_cmd( app, cmd );
}

static void* android_app_entry( void* param ) {
    android_app* app = static_cast< android_app* >( param );

    app->config = AConfiguration_new();
    AConfiguration_fromAssetManager( app->config, app->activity->assetManager );

    print_cur_config( app );

    app->cmdPollSource.id = LOOPER_ID_MAIN;
    app->cmdPollSource.app = app;
    app->cmdPollSource.process = process_cmd;
    app->inputPollSource.id = LOOPER_ID_INPUT;
    app->inputPollSource.app = app;
    app->inputPollSource.process = process_input;

    ALooper* looper = ALooper_prepare( ALOOPER_PREPARE_ALLOW_NON_CALLBACKS );
    ALooper_addFd( looper, app->msgread, LOOPER_ID_MAIN, ALOOPER_EVENT_INPUT, NULL, &app->cmdPollSource );
    app->looper = looper;

    pthread_mutex_lock( &app->mutex );
    app->running = 1;
    pthread_cond_broadcast( &app->cond );
    pthread_mutex_unlock( &app->mutex );

    android_main( app );

    android_app_destroy( app );
    return NULL;
}

// --------------------------------------------------------------------
// Native activity interaction (called from main thread)
// --------------------------------------------------------------------

android_app* android_app_create( ANativeActivity* activity, void* savedState, size_t savedStateSize ) {
	android_app* app = NEW android_app;
    memset( app, 0, sizeof( android_app ) );
    app->activity = activity;

    pthread_mutex_init( &app->mutex, NULL );
    pthread_cond_init( &app->cond, NULL );

    if ( savedState != NULL ) {
    	app->savedState = NEW unsigned char[ savedStateSize ];
    	app->savedStateSize = savedStateSize;
        memcpy( app->savedState, savedState, savedStateSize );
    }

    int msgpipe[2];
    if ( pipe( msgpipe ) ) {
        LOGI( "could not create pipe: %s", strerror( errno ) );
    }
    app->msgread = msgpipe[ 0 ];
    app->msgwrite = msgpipe[ 1 ];

    pthread_attr_t attr;
    pthread_attr_init( &attr );
    pthread_attr_setdetachstate( &attr, PTHREAD_CREATE_DETACHED );
    pthread_create( &app->thread, &attr, android_app_entry, app );

    // Wait for thread to start.
    pthread_mutex_lock( &app->mutex );
    while ( !app->running ) {
        pthread_cond_wait( &app->cond, &app->mutex );
    }
    pthread_mutex_unlock( &app->mutex );

    return app;
}

static void android_app_write_cmd( android_app* app, int8_t cmd ) {
    if ( write( app->msgwrite, &cmd, sizeof( cmd ) ) != sizeof( cmd ) ) {
        LOGI( "Failure writing android_app cmd: %s\n", strerror( errno ) );
    }
}

static void android_app_set_input( android_app* app, AInputQueue* inputQueue ) {
    pthread_mutex_lock( &app->mutex );
    app->pendingInputQueue = inputQueue;
    android_app_write_cmd( app, APP_CMD_INPUT_CHANGED );
    while ( app->inputQueue != app->pendingInputQueue ) {
        pthread_cond_wait( &app->cond, &app->mutex );
    }
    pthread_mutex_unlock( &app->mutex );
}

static void android_app_set_window( android_app* app, ANativeWindow* window ) {
    pthread_mutex_lock( &app->mutex );
    if ( app->pendingWindow != NULL ) {
        android_app_write_cmd( app, APP_CMD_TERM_WINDOW );
    }
    app->pendingWindow = window;
    if ( window != NULL ) {
        android_app_write_cmd( app, APP_CMD_INIT_WINDOW );
    }
    while ( app->window != app->pendingWindow ) {
        pthread_cond_wait( &app->cond, &app->mutex );
    }
    pthread_mutex_unlock( &app->mutex );
}

static void android_app_set_activity_state( android_app* app, int8_t cmd ) {
    pthread_mutex_lock( &app->mutex );
    android_app_write_cmd( app, cmd );
    while ( app->activityState != cmd ) {
        pthread_cond_wait( &app->cond, &app->mutex );
    }
    pthread_mutex_unlock( &app->mutex );
}

static void android_app_free( android_app* app ) {
    pthread_mutex_lock( &app->mutex );
    android_app_write_cmd( app, APP_CMD_DESTROY);
    while ( !app->destroyed ) {
        pthread_cond_wait( &app->cond, &app->mutex );
    }
    pthread_mutex_unlock( &app->mutex );

    close( app->msgread );
    close( app->msgwrite );
    pthread_cond_destroy( &app->cond );
    pthread_mutex_destroy( &app->mutex );
    SAFE_DELETE( app );
}

static void onDestroy( ANativeActivity* activity ) {
    LOGI( "Destroy: %p\n", activity );
    android_app_free( static_cast< android_app* >( activity->instance ) );
    activity->instance = NULL;
    // onDestroyしたときに一旦出す
    MemoryManager().write();
}

static void onStart( ANativeActivity* activity ) {
    LOGI( "Start: %p\n", activity );
    android_app_set_activity_state( static_cast< android_app* >( activity->instance ), APP_CMD_START );
}

static void onResume( ANativeActivity* activity ) {
    LOGI( "Resume: %p\n", activity );
    android_app_set_activity_state( static_cast< android_app* >( activity->instance ), APP_CMD_RESUME );
}

static void* onSaveInstanceState( ANativeActivity* activity, size_t* outLen ) {
    android_app* app = static_cast< android_app* >( activity->instance );
    void* savedState = NULL;

    LOGI( "SaveInstanceState: %p\n", activity );
    pthread_mutex_lock( &app->mutex );
    app->stateSaved = 0;
    android_app_write_cmd( app, APP_CMD_SAVE_STATE );
    while ( !app->stateSaved ) {
        pthread_cond_wait( &app->cond, &app->mutex );
    }

    if (app->savedState != NULL) {
        savedState = app->savedState;
        *outLen = app->savedStateSize;
        app->savedState = NULL;
        app->savedStateSize = 0;
    }

    pthread_mutex_unlock( &app->mutex );

    return savedState;
}

static void onPause( ANativeActivity* activity ) {
    LOGI( "Pause: %p\n", activity );
    android_app_set_activity_state( static_cast< android_app* >( activity->instance ), APP_CMD_PAUSE );
}

static void onStop( ANativeActivity* activity ) {
    LOGI( "Stop: %p\n", activity );
    android_app_set_activity_state( static_cast< android_app* >( activity->instance ), APP_CMD_STOP );
}

static void onConfigurationChanged( ANativeActivity* activity ) {
    LOGI( "ConfigurationChanged: %p\n", activity );
    android_app_write_cmd( static_cast< android_app* >( activity->instance ), APP_CMD_CONFIG_CHANGED );
}

static void onLowMemory( ANativeActivity* activity ) {
    LOGI( "LowMemory: %p\n", activity );
    android_app_write_cmd( static_cast< android_app* >( activity->instance ), APP_CMD_LOW_MEMORY );
}

static void onWindowFocusChanged( ANativeActivity* activity, int focused ) {
    LOGI( "WindowFocusChanged: %p -- %d\n", activity, focused );
    android_app_write_cmd( static_cast< android_app* >( activity->instance ),
    		focused ? APP_CMD_GAINED_FOCUS : APP_CMD_LOST_FOCUS );
}

static void onNativeWindowCreated( ANativeActivity* activity, ANativeWindow* window ) {
    LOGI( "NativeWindowCreated: %p -- %p\n", activity, window );
    android_app_set_window( static_cast< android_app* >( activity->instance ), window);
}

static void onNativeWindowDestroyed( ANativeActivity* activity, ANativeWindow* window ) {
    LOGI( "NativeWindowDestroyed: %p -- %p\n", activity, window );
    android_app_set_window( static_cast< android_app* >( activity->instance ), NULL);
}

static void onNativeWindowRedrawNeeded( ANativeActivity* activity, ANativeWindow* window ) {
    LOGI( "NativeWindowRedrawNeeded: %p -- %p\n", activity, window );
    android_app_write_cmd( static_cast< android_app* >( activity->instance ), APP_CMD_WINDOW_REDRAW_NEEDED );
}

static void onInputQueueCreated(ANativeActivity* activity, AInputQueue* queue) {
    LOGI( "InputQueueCreated: %p -- %p\n", activity, queue );
    android_app_set_input( static_cast< android_app* >( activity->instance ), queue );
}

static void onInputQueueDestroyed( ANativeActivity* activity, AInputQueue* queue ) {
    LOGI( "InputQueueDestroyed: %p -- %p\n", activity, queue );
    android_app_set_input( static_cast< android_app* >( activity->instance ), NULL);
}

/**
 * コアクラスの実体
 */
class Impl {
public:
	Impl( android_app* app )
	: mApp( app )
	, mSensorManager( 0 )
	, mAccelerometerSensor( 0 )
	, mSensorEventQueue( 0 )
	, mIsEnded( false )
	, mIsRequestedEnd( false )
	, mUseAccelerometerSensor( true )
	, mIsChangeDisplayMode( false )
	, mPaused( false )
	, mHasFocus( false )
	{
		mSensorManager = ASensorManager_getInstance();
		mAccelerometerSensor = ASensorManager_getDefaultSensor( mSensorManager, ASENSOR_TYPE_ACCELEROMETER );
		mSensorEventQueue = ASensorManager_createEventQueue( mSensorManager, app->looper, LOOPER_ID_USER, NULL, NULL );

		// 起動時間を取得
		CLOCK_TIMEGET( mTimeStart );

		// メインスレッドIDを取得
		mThreadId = pthread_self();
	}

	~Impl() {
		if ( mSensorEventQueue  ) {
			ASensorManager_destroyEventQueue( mSensorManager, mSensorEventQueue );
		}
		mSensorEventQueue = NULL;
		mAccelerometerSensor = NULL;
		mSensorManager = NULL;
	}

	bool isMainThread() const {
		return ( pthread_self() == mThreadId );
	}

	bool poll() {
		int ident;
		int events;
		android_poll_source* source;
		while ( ( ident = ALooper_pollAll( 0, NULL, &events, (void**)&source ) ) >= 0 ) {
			// process this event.
			if ( source ) {
				source->process( mApp, source );
			}

			// If a sensor has data, process it now.
			if ( ident == LOOPER_ID_USER ) {
				if ( mAccelerometerSensor != NULL ) {
					while ( ASensorEventQueue_getEvents( mSensorEventQueue, &mASensorEvent, 1 ) > 0 ) {
//						LOGI("accelerometer: x=%f y=%f z=%f", event.acceleration.x, event.acceleration.y, event.acceleration.z);
					}
				}
			}

			// 終了要求がきたので抜ける
			if ( mApp->destroyRequested ) {
				break;
			}
		}
		return mApp->destroyRequested ? true : false;
	}

	void requestEnd() {
		mIsRequestedEnd = true;
	}

	android_app* mApp;

    ASensorManager* mSensorManager;
    const ASensor* mAccelerometerSensor;
    ASensorEventQueue* mSensorEventQueue;

	pthread_t mThreadId;
	bool mIsEnded; // 終了か
	bool mIsRequestedEnd; // 終了要求がきたか
	bool mUseAccelerometerSensor;
	bool mIsChangeDisplayMode;
	bool mPaused;
	bool mHasFocus;
	timespec mTimeStart;
	ASensorEvent mASensorEvent;
};

static Impl* gImpl = 0;

static void engine_handle_cmd( android_app* app, int32_t cmd ) {
	ASSERT( gImpl );
	ASSERT( gImpl->mApp == app );
	switch (cmd) {
		case APP_CMD_SAVE_STATE:
			// The system has asked us to save our current state.
			break;
		case APP_CMD_INIT_WINDOW:
			// The window is being shown, get it ready.
			if ( app->window ) {
				gImpl->mPaused = false;
			}
			break;
		case APP_CMD_TERM_WINDOW:
			// The window is being hidden or closed, clean it up.
			gImpl->mPaused = true;
			break;
		case APP_CMD_PAUSE:
			gImpl->mPaused = true;
			break;
		case APP_CMD_RESUME:
			gImpl->mPaused = false;
			break;
		case APP_CMD_WINDOW_REDRAW_NEEDED:
			gImpl->mIsChangeDisplayMode = true;
			break;
		case APP_CMD_GAINED_FOCUS:
			// When our app gains focus, we start monitoring the accelerometer.
			if ( gImpl->mAccelerometerSensor ) {
				ASensorEventQueue_enableSensor( gImpl->mSensorEventQueue, gImpl->mAccelerometerSensor );
				// We'd like to get 60 events per second (in us).
				int32_t usec = ( 1000 * 1000 ) / 60;
				ASensorEventQueue_setEventRate( gImpl->mSensorEventQueue, gImpl->mAccelerometerSensor, usec );
			}
			gImpl->mHasFocus = true;
			break;
		case APP_CMD_LOST_FOCUS:
			// When our app loses focus, we stop monitoring the accelerometer.
			// This is to avoid consuming battery while not being used.
			if ( gImpl->mAccelerometerSensor ) {
				ASensorEventQueue_disableSensor( gImpl->mSensorEventQueue, gImpl->mAccelerometerSensor );
			}
			// Also stop animating.
			gImpl->mHasFocus = false;
			break;
	}
}

// メインループ
void android_main( android_app* state ) {
	// コアの実体を生成
	gImpl = NEW Impl( state );
	Core core;

	// コンフィグ設定
	core.configure( Core::Configuration() );

	// コールバックを設定
	state->onAppCmd = engine_handle_cmd;
	state->onInputEvent = NULL;

	// アプリケーションの初期化処理を呼ぶ
	try {
		core.start( gImpl->mApp );
	} catch( ... ) {
		gImpl->requestEnd();
		try {
			core.update();
		} catch ( ... ) {
			// もう終了するので無視する
		}
		// 終了準備
		ANativeActivity_finish( state->activity );
	}

	// メインループ
	while ( !gImpl->poll() ) {
		// アプリから終了要求きた？
		if ( gImpl->mIsEnded ) {
			// NativeActivityに終了要求をだす
			ANativeActivity_finish( state->activity );
		} else {
			// メイン処理
			try {
				core.update();
			} catch ( Exception e ) {
				if ( e = EXCEPTION_EXIT ) {
					// TODO:ここにくるようなのは実行に支障をきたすので終了させる
				}
			}
		}
	}

	// 開放
	core.stop();
	SAFE_DELETE( gImpl );
}

} // namespace { // 無名

Core::Core() {
	ASSERT( isMainThread() && "you must call from MAIN thread" );
}

Core Core::instance() {
	return Core();
}

bool Core::isMainThread() {
	return gImpl->isMainThread();
}

bool Core::isRequestedEnd() const {
	return gImpl->mIsRequestedEnd;
}

void Core::requestEnd() {
	gImpl->requestEnd();
}

void Core::finish() {
	gImpl->mIsEnded = true;
}

unsigned Core::time() const {
	timespec ts;
	CLOCK_TIMEGET( ts );
//	unsigned mtime = difftime( ts.tv_sec, gImpl->mTimeStart.tv_sec ) * 1000;
	unsigned mtime = ( ts.tv_sec - gImpl->mTimeStart.tv_sec ) * 1000;
	mtime += ( ts.tv_nsec - gImpl->mTimeStart.tv_nsec ) / 1000000;
	return mtime;
}

bool Core::isActive() const {
	// ポーズ中じゃなくて、フォーカスをもらってて、終了要求がなければアクティブだと判定
	return !gImpl->mPaused && gImpl->mHasFocus && !gImpl->mIsEnded;
}

bool Core::isPause() const {
	return gImpl->mPaused;
}

bool Core::hasFocus() const {
	return gImpl->mHasFocus;
}

void Core::requestChangeDisplayMode( bool f ) {
	gImpl->mIsChangeDisplayMode = f;
}

bool Core::isRequestedChangeDisplayMode() const {
	return gImpl->mIsChangeDisplayMode;
}

}// namespace NTEngine

using namespace NTEngine;

/**
 * onCreate時に呼ばれる
 */
void ANativeActivity_onCreate( ANativeActivity* activity, void* savedState, size_t savedStateSize ) {
    LOGI( "Creating: %p\n", activity );
    activity->callbacks->onDestroy = onDestroy;
    activity->callbacks->onStart = onStart;
    activity->callbacks->onResume = onResume;
    activity->callbacks->onSaveInstanceState = onSaveInstanceState;
    activity->callbacks->onPause = onPause;
    activity->callbacks->onStop = onStop;
    activity->callbacks->onConfigurationChanged = onConfigurationChanged;
    activity->callbacks->onLowMemory = onLowMemory;
    activity->callbacks->onWindowFocusChanged = onWindowFocusChanged;
    activity->callbacks->onNativeWindowCreated = onNativeWindowCreated;
    activity->callbacks->onNativeWindowDestroyed = onNativeWindowDestroyed;
    activity->callbacks->onNativeWindowRedrawNeeded = onNativeWindowRedrawNeeded; // SurfaceChanged
    activity->callbacks->onInputQueueCreated = onInputQueueCreated;
    activity->callbacks->onInputQueueDestroyed = onInputQueueDestroyed;

    activity->instance = android_app_create( activity, savedState, savedStateSize );
}
